// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Data;

namespace SnapObjects.Data.DB2
{
    /// <summary>
    /// 
    /// </summary>
    public class DB2TypeMapper : TypeMapper
    {
        private readonly DB2StringTypeMapping _nvarcharmax = new DB2StringTypeMapping("nvarchar(max1)", DbType.String);
        private readonly DB2StringTypeMapping _nvarchar450 = new DB2StringTypeMapping("nvarchar(450)", DbType.String, unicode: false, 450);
        private readonly DB2StringTypeMapping _varcharmax = new DB2StringTypeMapping("varchar(max1)", DbType.AnsiString);
        private readonly DB2StringTypeMapping _varchar900 = new DB2StringTypeMapping("varchar(900)", DbType.AnsiString);
        private readonly DB2StringTypeMapping _nchar = new DB2StringTypeMapping("nchar", DbType.StringFixedLength);
        private readonly DB2StringTypeMapping _nvarchar = new DB2StringTypeMapping("nvarchar", DbType.String);
        private readonly DB2StringTypeMapping _clob = new DB2StringTypeMapping("clob", DbType.String);
        private readonly DB2StringTypeMapping _dbclob = new DB2StringTypeMapping("dbclob", DbType.String);
        private readonly DB2StringTypeMapping _graphic = new DB2StringTypeMapping("graphic", DbType.String);
        private readonly DB2StringTypeMapping _vargraphic = new DB2StringTypeMapping("vargraphic", DbType.String);
        private readonly DB2StringTypeMapping _longvarg = new DB2StringTypeMapping("longvarg", DbType.String);
        private readonly DB2StringTypeMapping _longvar = new DB2StringTypeMapping("longvar", DbType.StringFixedLength);
        private readonly DB2StringTypeMapping _datalink = new DB2StringTypeMapping("datalink", DbType.String);
        private readonly DB2StringTypeMapping _varg = new DB2StringTypeMapping("varg", DbType.String);
        private readonly DB2StringTypeMapping _char = new DB2StringTypeMapping("char", DbType.AnsiStringFixedLength);
        private readonly DB2StringTypeMapping _varchar = new DB2StringTypeMapping("varchar", DbType.AnsiString);
        private readonly DB2StringTypeMapping _interval = new DB2StringTypeMapping("interval", DbType.String);
        private readonly DB2StringTypeMapping _xml = new DB2StringTypeMapping("xml", DbType.String);

        private readonly DB2ByteArrayTypeMapping _varbinarymax = new DB2ByteArrayTypeMapping("varbinary(max)", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _varbinary900 = new DB2ByteArrayTypeMapping("varbinary(900)", DbType.Binary, 900);
        private readonly DB2ByteArrayTypeMapping _rowversion = new DB2ByteArrayTypeMapping("rowversion", DbType.Binary, 8);
        private readonly DB2ByteArrayTypeMapping _varbinary = new DB2ByteArrayTypeMapping("varbinary", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _varbin = new DB2ByteArrayTypeMapping("varbin", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _binary = new DB2ByteArrayTypeMapping("binary", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _rowid = new DB2ByteArrayTypeMapping("rowid", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _blob = new DB2ByteArrayTypeMapping("blob", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _charforbitdata = new DB2ByteArrayTypeMapping("char for bit data", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _characterforbitdata = new DB2ByteArrayTypeMapping("character for bit data", DbType.Binary);
        private readonly DB2ByteArrayTypeMapping _varcharforbitdata = new DB2ByteArrayTypeMapping("varchar for bit data", DbType.Binary);

        private readonly IntTypeMapping _int = new IntTypeMapping("int", DbType.Int32);
        private readonly LongTypeMapping _bigint = new LongTypeMapping("bigint", DbType.Int64);
        private readonly BoolTypeMapping _boolean = new DB2BoolTypeMapping("boolean", DbType.Boolean);
        private readonly ShortTypeMapping _bit = new ShortTypeMapping("bit", DbType.Int16);
        private readonly ShortTypeMapping _smallint = new ShortTypeMapping("smallint", DbType.Int16);
        private readonly ByteTypeMapping _byte = new ByteTypeMapping("tinyint", DbType.Byte);
        private readonly DoubleTypeMapping _double = new DB2DoubleTypeMapping("float");
        private readonly TypeMapping _real = new DB2FloatTypeMapping("real", IBM.Data.DB2.Core.DB2Type.Float);
        private readonly TypeMapping _uniqueidentifier = new GuidTypeMapping("uniqueidentifier", DbType.Guid);
        private readonly DecimalTypeMapping _decimal = new DecimalTypeMapping("decimal(18, 2)", DbType.Decimal);
        private readonly DecimalTypeMapping _decfloat = new DecimalTypeMapping("decimal(30, 0)", DbType.Decimal);

        private readonly DB2DateTimeTypeMapping _datetime = new DB2DateTimeTypeMapping("datetime", DbType.DateTime);
        private readonly DB2DateTimeTypeMapping _date = new DB2DateTimeTypeMapping("date", DbType.Date);
        private readonly TimeSpanTypeMapping _time = new DB2TimeSpanTypeMapping("time");
        private readonly DateTimeTypeMapping _timestamp = new DB2DateTimeTypeMapping("timestamp(6)", DbType.DateTime);
        private readonly DateTimeTypeMapping _timestmp = new DB2DateTimeTypeMapping("timestmp", DbType.DateTime);
        private readonly DateTimeTypeMapping _timestampmax = new DB2DateTimeTypeMapping("timestamp(max)", DbType.DateTime);
        private readonly DB2DateTimeOffsetTypeMapping _timestz = new DB2DateTimeOffsetTypeMapping("timestz", DbType.DateTimeOffset);
        private readonly DB2DateTimeOffsetTypeMapping _datetimeoffset = new DB2DateTimeOffsetTypeMapping("datetimeoffset", DbType.DateTimeOffset);

        private readonly HashSet<string> _disallowedMappings;
        private readonly Dictionary<string, TypeMapping> _dbTypeMappings;
        private readonly Dictionary<Type, TypeMapping> _clrTypeMappings;

        public DB2TypeMapper(IDataContextOptions options) : base(options)
        {
            _dbTypeMappings = new Dictionary<string, TypeMapping>(StringComparer.OrdinalIgnoreCase)
            {
                {"nvarchar(max1)", _nvarcharmax},
                {"nvarchar(450)", _nvarchar450 },
                {"varchar(max1)", _varcharmax },
                {"varchar(900)", _varchar900 },
                {"varbinary(max)", _varbinarymax },
                {"varbinary(900)", _varbinary900 },
                {"rowversion", _rowversion },
                {"int", _int },
                {"integer", _int },
                {"bigint", _bigint},
                {"boolean", _boolean},
                {"bit", _bit},
                {"smallint", _smallint},
                {"tinyint", _byte},
                {"nchar", _nchar},
                {"nvarchar", _nvarchar },
                {"clob", _clob },
                {"dbclob", _dbclob },
                {"graphic", _graphic },
                {"vargraphic", _vargraphic },
                {"longvarg", _longvarg },
                {"longvar", _longvar },
                {"datalink", _datalink },
                {"varg", _varg },
                {"char", _char },
                {"varchar", _varchar },
                {"varbinary", _varbinary},
                {"varbin", _varbin },
                {"binary", _binary },
                {"rowid", _rowid },
                {"blob", _blob },
                {"char for bit data", _charforbitdata },
                {"character for bit data", _characterforbitdata },
                {"varchar for bit data", _varcharforbitdata},
                {"datetime", _datetime },
                {"date", _date},
                {"float", _double},
                {"datetimeoffset", _datetimeoffset },
                {"real", _real},
                {"uniqueidentifier", _uniqueidentifier},
                {"decimal(18, 2)", _decimal},
                {"decimal(30, 0)", _decfloat },
                {"time", _time },
                {"timestamp(6)", _timestamp },
                {"timestmp", _timestmp },
                {"timestamp(max)", _timestampmax},
                {"interval", _interval},
                {"timestz", _timestz},
                {"xml", _xml}
            };

            // Note: sbyte, ushort, uint, char and ulong type mappings are not supported by SQL Server.
            // We would need the type conversions feature to allow this to work - see https://github.com/aspnet/EntityFramework/issues/242.
            _clrTypeMappings
                = new Dictionary<Type, TypeMapping>
                {
                    {typeof(byte[]), _blob },
                    { typeof(DateTime), _datetime },
                    { typeof(decimal), _decimal },
                    { typeof(double), _double },
                    { typeof(short), _smallint },
                    { typeof(int), _int },
                    { typeof(long), _bigint },
                    { typeof(string), _varchar},
                    { typeof(TimeSpan), _time},
                    { typeof(float), _real},
                    { typeof(bool), _boolean}
                };

            // These are disallowed only if specified without any kind of length specified in parenthesis.
            // This is because we don't try to make a new type from this string and any max length value
            // specified in the model, which means use of these strings is almost certainly an error, and
            // if it is not an error, then using, for example, varbinary(1) will work instead.
            _disallowedMappings
                = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
                {
                    /*"binary varying",
                    "binary",
                    "char varying",
                    "char",
                    "character varying",
                    "character",
                    "national char varying",
                    "national character varying",
                    "national character",
                    "nchar",
                    "nvarchar",
                    "varbinary",
                    "varchar"*/
                };
        }

        public override void ValidateTypeName(string dbTypeName)
        {
            if (_disallowedMappings.Contains(dbTypeName))
            {
                throw new ArgumentException(DB2Strings.UnqualifiedDataType(dbTypeName));
            }
        }

        protected override IReadOnlyDictionary<Type, TypeMapping> GetClrTypeMappings()
        {
            return _clrTypeMappings;
        }

        protected override IReadOnlyDictionary<string, TypeMapping> GetDbTypeMappings()
        {
            return _dbTypeMappings;
        }
    }
}
